旧游无处不堪寻
无寻处,惟有少年心
C Sharp(十三)

这一篇,我们看看 C# 中的枚举器和迭代器的基本概念。
之前我们说过可以使用 foreach 来遍历数组元素,本篇来讨论为什么数组可以使用 foreach 语句处理,我们可以还使用迭代器来使得自定义类型也可以使用 foreach。

枚举器和可枚举类型


为什么数组可以使用 foreach 呢?因为数组可以提供一个枚举器(enumerator)对象。枚举器对象可以依次返回数组元素。
获取一个对象的枚举器可以调用对象的 GetEnumerator 方法。实现了 GetEnumerator 方法的对象称为可枚举(enumerable)对象。

foreach 语句就是用来配合可枚举类型一起使用的,他会执行下列行为:

  • 调用 GetEnumerator 方法获取对象的枚举器
  • 从枚举器请求每一项作为迭代变量(iteration variable),我们可以读取该变量但不能改变
foreach (Type ValName in EnumerableObject) {
//...
}

IEnumerator

实现 IEnumerator 接口的枚举器包含三个函数成员:

  • Current: 返回当前位置项的属性,只读
  • MoveNext: 把枚举器位置前进到集合下一项的方法,返回布尔值,位置有效返回 true,无效(到达尾部)返回 false。枚举器原始位置在第一项之前,因此在使用 Current 之前必须先调用 MoveNext
  • Reset: 位置重置为原始状态

下面代码与直接使用 foreach 产生的结果是一样的:

using System.Collections;

class Program
{
static void Main(string[] args)
{
int[] MyArr = { 1, 2, 3, 5, 6, 7 };
IEnumerator ie = MyArr.GetEnumerator();
while (ie.MoveNext())
{
int current = (int)ie.Current;
Console.WriteLine(current);
}
}
}

IEnumerable

可枚举类型是指实现了 IEnumerable 接口的类。IEnumerable 只有一个函数成员:

  • GetEnumerator: 获取可枚举类型的枚举器
using System.Collections;

class MyClass : IEnumerable
{
public IEnumerator GetEnumerator()
{
//...
}
}

使用 IEnumerator 和 IEnumerable 示例

using System.Collections;

class ColorEnumerator : IEnumerator
{
private string[] _colors;
private int _position = -1;
public ColorEnumerator(string[] colors)
{
_colors = new string[colors.Length];
for (int i = 0; i < colors.Length; i++) {
_colors[i] = colors[i];
}
}

public object Current {
get {
if (_position <= -1 || _position >= _colors.Length) {
throw new InvalidOperationException();
}
return _colors[_position];
}
}

public bool MoveNext(()
{
if (_position < _colors.Length - 1) {
_position++;
return true;
}
return false;
}

public void Reset()
{
_position = -1;
}
}

class Colors : IEnumerable
{
private string[] _colors;
public Colors(string[] colors)
{
_colors = new string[colors.Length];
for (int i = 0; i < colors.Length; i++) {
_colors[i] = colors[i];
}
}

public IEnumerator GetEnumerator()
{
return new ColorEnumerator(_colors);
}
}

class Progeam
{
static void Main()
{
Colors colors = new Colors(new string[] {"red", "green", "blue", "yellow"});
foreach (string color in colors) {
Console.WriteLine(color);
}
}
}

泛型枚举接口

之前我们写的都是非泛型版本,实际工作中,我们基本都使用泛型版本的 IEnumerator 和 IEnumerable 。非泛型版本只是兼任 2.0 版本之前无泛型的遗留代码。

泛型与非泛型版本的主要区别是:

  • IEnumerable 接口的 GetEnumerator 方法要返回实现 IEnumerator 接口的枚举器实例
  • 泛型版本的 Current 属性返回的不是 object 类型,而是实际类型的对象

迭代器

C# 2.0 之后,提供了更简单的创建枚举器和可枚举类型的方式。这种结构称为迭代器(iterator)。

  • 迭代器返回一个泛型的枚举器
  • yield return 语句声明这是枚举的下一项
public IEnumerator<string> BlackAndWhite()
{
yield return "black";
yield return "gray";
yield return "white";
}